/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#include "commands.h"
#include "utils.h"
#include "validatornode.h"
#include <QObject>
#include <QDebug>
ChangeAttributeCommand::ChangeAttributeCommand(const QModelIndex &index, const QModelIndex& nodes_model_parent, const QString &val):
    NodeChangedBaseCommand (nodes_model_parent),
    attributes_model(AttributesModel::getAttributesModel(index)),
    attr(attributes_model->getAttribute(index)),
    nodes_model(attributes_model->getNodesModel()),
    new_val(val),
    old_val(QString::fromStdString(attr->getValue()))
{
    MemoryNode* node = attr->getParentNode();
    setText("Attribute " + QString::fromStdString(attr->getName()) + "'s val " + old_val + " -> " + new_val + " in " + QString::fromStdString(node->getName()));
}

void ChangeAttributeCommand::redo()
{
    setAttribute(new_val);
}

void ChangeAttributeCommand::undo()
{
    setAttribute(old_val);
}

void ChangeAttributeCommand::setAttribute(const QString& value)
{
    const QModelIndex& index = attributes_model->getIndex(attr);
    if(index.isValid())
    {
        attr->setValue(value.toStdString());
        Q_EMIT attributes_model->dataChanged(index, index.siblingAtColumn(1));
        // Q_EMIT dataChanged for TreeModel done in TableModel - connected dataChanged
    }
}

AddChildCommand::AddChildCommand(const int& position, const QString &type, const QModelIndex &nodes_model_parent):
    NodeChangedBaseCommand (nodes_model_parent),
    position(position), type(type), model(NodesModel::getNodesModel(nodes_model_parent)), parent_index(nodes_model_parent),
    parent_node(model->getItem(parent_index))
{
    QString parent_name;
    for(auto& str: model->getItem(parent_index)->nodeLocation())
    {
        parent_name += QString::fromStdString(str) + "_";
    }
    setText("Added " + type + " to " + parent_name);
}

void AddChildCommand::redo()
{
    if(!parent_index.isValid())
    {
        parent_index = model->getIndex(parent_node).siblingAtColumn(0);
    }
    if(parent_index.isValid())
    {
        if(node_copy) // redoing undone...
            model->insertChild(position, std::move(node_copy), parent_index);
        else
            model->addChild(position, type, parent_index);
    }
}

void AddChildCommand::undo()
{
    if(!parent_index.isValid())
    {
        parent_index = model->getIndex(parent_node).siblingAtColumn(0);
    }
    if(parent_index.isValid())
        node_copy = model->removeChild(position, parent_index);
}

MoveCommand::MoveCommand(MoveCommand::MoveDirection direction, QPersistentModelIndex index, int count):
    NodeChangedBaseCommand (index.parent()), dir(direction), index(index), count(count), nodes_model(NodesModel::getNodesModel(index))
{
    QString dir_str;
    if(dir == up)
        dir_str = "up";
    else
        dir_str = "down";
    setText(QObject::tr("Node(s) [%1:%2] (%3) moved %4")
            .arg(QString::fromStdString(nodes_model->getItem(index)->getName()))
            .arg(QString::fromStdString(nodes_model->getItem(index.sibling(index.row()+count-1, 0))->getName()))
            .arg(QString::number(count))
            .arg(dir_str));
}

void MoveCommand::redo()
{
    move(dir);
}

void MoveCommand::undo()
{
    move(reverse_dir(dir));
}

void MoveCommand::move(MoveCommand::MoveDirection direction)
{
    if(index.isValid())
    {
        if(direction == down)
        {
            // shift destination position by count -1
            nodes_model->moveRows(index.parent(), index.row(), count, index.parent(), index.row() + direction + count - 1);
        }
        else
        {
            // moving up, so no shifting needed
            nodes_model->moveRows(index.parent(), index.row(), count, index.parent(), index.row() + direction);
        }
    }
}

MoveCommand::MoveDirection MoveCommand::reverse_dir(MoveCommand::MoveDirection dir)
{
    if(dir == up)
        return down;
    return up;
}

RemoveNodeCommand::RemoveNodeCommand(QPersistentModelIndex index):
    NodeChangedBaseCommand (index.parent()),
    parent(index.parent()), row(index.row()), nodes_model(NodesModel::getNodesModel(index))
{
    setText("Removed node " + QString::fromStdString(nodes_model->getItem(index)->getName()));
}

void RemoveNodeCommand::redo()
{
    if(parent.isValid())
    {
        node_copy = nodes_model->removeChild(row, parent);
    }
}

void RemoveNodeCommand::undo()
{
    if(parent.isValid())
    {
        nodes_model->insertChild(row, std::move(node_copy), parent);
    }
}


DuplicateNodeCommand::DuplicateNodeCommand(QPersistentModelIndex index, int targetPosition, QPersistentModelIndex target_index):
    NodeChangedBaseCommand (target_index),
    row(targetPosition), index(index), target_index(target_index), nodes_model(NodesModel::getNodesModel(index))
{
    setText("Duplicated node " + QString::fromStdString(nodes_model->getItem(index)->getName()));
}

void DuplicateNodeCommand::redo()
{
    if(target_index.isValid())
    {
        if(node_copy) // redoing undone...
            nodes_model->insertChild(row, std::move(node_copy), target_index); // parent here!!!
        else
            nodes_model->duplicateChild(row, index, target_index);
    }
}

void DuplicateNodeCommand::undo()
{
    if(target_index.isValid())
    {
        node_copy = nodes_model->removeChild(row, target_index);
    }
}

AddAttributeCommand::AddAttributeCommand(const QString name, const QModelIndex& node_index, const QModelIndex& container_index, AttributesModel* model, const QString value):
    NodeChangedBaseCommand (node_index),
    name(name),
    value(value),
    attributes_model(model),
    container_name(attributes_model->getContainer(container_index)->getFullName()),
    parent_container_index(container_index),
    hasValidParent(container_index.isValid())
{
    setText("Added attribute " + QString::fromStdString(join(container_name, "/")) + "/" + name + " to " + QString::fromStdString(NodesModel::getNodesModel(node_index)->getItem(node_index)->getName()));
}

void AddAttributeCommand::redo()
{
    if(hasValidParent && !parent_container_index.isValid())
    {
        const MemoryNode* node = attributes_model->getParentMemoryNode();
        AttributeContainer* parent_container = node->getAttributeContainer()->getAttributeContainer(container_name);
        parent_container_index = attributes_model->getIndex(parent_container);
    }
    if(!hasValidParent || parent_container_index.isValid())
    {
        if(attribute_copy)
            attributes_model->addAttribute(parent_container_index, std::move(attribute_copy));
        else
            attributes_model->addAttribute(parent_container_index, name, value);
    }
}

void AddAttributeCommand::undo()
{
    if(hasValidParent && !parent_container_index.isValid())
    {
        const MemoryNode* node = attributes_model->getParentMemoryNode();
        AttributeContainer* parent_container = node->getAttributeContainer()->getAttributeContainer(container_name);
        parent_container_index = attributes_model->getIndex(parent_container);
    }
    if(!hasValidParent || parent_container_index.isValid())
        attribute_copy = attributes_model->removeAttribute(parent_container_index, name);
}

RemoveAttributeCommand::RemoveAttributeCommand(const QModelIndex& index, const QModelIndex& tree_node_parent):
    NodeChangedBaseCommand (tree_node_parent),
    attributes_model(AttributesModel::getAttributesModel(index)),
    hasValidParent(index.parent().isValid()),
    container_name(attributes_model->getContainer(index.parent())->getFullName()),
    parent_container_index(index.parent())

{
    Attribute* attr = attributes_model->getAttribute(index);
    name = QString::fromStdString(attr->getName());
    setText("Removed attribute " + QString::fromStdString(join(container_name, "/")) + "/" + name + " from " + QString::fromStdString(attr->getParentNode()->getName()));
}

void RemoveAttributeCommand::redo()
{
    if(hasValidParent && !parent_container_index.isValid())
    {
        const MemoryNode* node = attributes_model->getParentMemoryNode();
        AttributeContainer* container = node->getAttributeContainer()->getAttributeContainer(container_name);
        parent_container_index = attributes_model->getIndex(container);
    }
    if(!hasValidParent || parent_container_index.isValid())
    {
        attribute_copy = attributes_model->removeAttribute(parent_container_index, name);
    }
}

void RemoveAttributeCommand::undo()
{
    if(hasValidParent && !parent_container_index.isValid())
    {
        const MemoryNode* node = attributes_model->getParentMemoryNode();
        AttributeContainer* container = node->getAttributeContainer()->getAttributeContainer(container_name);
        parent_container_index = attributes_model->getIndex(container);
    }
    if(!hasValidParent || parent_container_index.isValid())
    {
        attributes_model->addAttribute(parent_container_index, std::move(attribute_copy));
    }
}

DragNodeCommand::DragNodeCommand(const QModelIndex &sourceParent, int sourcePosition, int sourceCount, const QModelIndex &destinationParent, int destPosition):
    NodeChangedBaseCommand ({sourceParent, destinationParent}),
    sourceParent(sourceParent),
    sourcePosition(sourcePosition),
    sourceCount(sourceCount),
    destinationParent(destinationParent),
    destPosition(destPosition),
    nodes_model(NodesModel::getNodesModel(sourceParent))
{
    setText(QObject::tr("Node drag&drop from %1 [%2:%3] to %4 [%5:%6]")
            .arg(QString::fromStdString(nodes_model->getItem(sourceParent)->getName()))
            .arg(QString::number(sourcePosition))
            .arg(QString::number(sourcePosition+sourceCount-1))
            .arg(QString::fromStdString(nodes_model->getItem(destinationParent)->getName()))
            .arg(QString::number(destPosition))
            .arg(QString::number(destPosition+sourceCount-1)));
}

void DragNodeCommand::redo()
{
    if(sourceParent.isValid() && destinationParent.isValid())
    {
        const bool result = nodes_model->moveRows(sourceParent, sourcePosition, sourceCount, destinationParent, destPosition);
        if(!result)
            setObsolete(true);
    }
    else
    {
        //qDebug() << "drag redo failed - not valid";
        setObsolete(true);
    }
}

void DragNodeCommand::undo()
{
    if(sourceParent.isValid() && destinationParent.isValid())
    {
        bool result;
        if(sourceParent == destinationParent)
        {
            result = nodes_model->moveRows(destinationParent, destPosition-sourceCount, sourceCount, sourceParent, sourcePosition);
        }
        else
        {
            result = nodes_model->moveRows(destinationParent, destPosition, sourceCount, sourceParent, sourcePosition);
        }
        if(!result)
            setObsolete(true);
    }
    else
    {
        //qDebug() << "drag undo failed - not valid";
        setObsolete(true);
    }
}

NodeChangedBaseCommand::NodeChangedBaseCommand(const QModelIndex &index): node_index_list({index})
{

}

NodeChangedBaseCommand::NodeChangedBaseCommand(const QPersistentModelIndex &index): node_index_list({index})
{

}

NodeChangedBaseCommand::NodeChangedBaseCommand(std::initializer_list<QPersistentModelIndex> l): node_index_list(l)
{

}


RemoveAttributeContainerCommand::RemoveAttributeContainerCommand(const QModelIndex &index, const QModelIndex &tree_node_parent):
    NodeChangedBaseCommand (tree_node_parent),
    attributes_model(AttributesModel::getAttributesModel(index)),
    hasValidParent(index.parent().isValid()),
    parent_container_index(index.parent()),
    parent_container_name(attributes_model->getContainer(parent_container_index)->getFullName())
{
    AttributeContainer* container = attributes_model->getContainer(index);
    name = QString::fromStdString(container->getName());
    setText("Removed attribute container " + name + " from " + QString::fromStdString(container->getParentNode()->getName()));
}

void RemoveAttributeContainerCommand::redo()
{
    if(hasValidParent && !parent_container_index.isValid())
    {
        const MemoryNode* node = attributes_model->getParentMemoryNode();
        AttributeContainer* parent_container = node->getAttributeContainer()->getAttributeContainer(parent_container_name);
        parent_container_index = attributes_model->getIndex(parent_container);
    }
    if(!hasValidParent || parent_container_index.isValid())
        attribute_container_copy = attributes_model->removeAttributeContainer(parent_container_index, name);
}

void RemoveAttributeContainerCommand::undo()
{
    if(hasValidParent && !parent_container_index.isValid())
    {
        const MemoryNode* node = attributes_model->getParentMemoryNode();
        AttributeContainer* parent_container = node->getAttributeContainer()->getAttributeContainer(parent_container_name);
        parent_container_index = attributes_model->getIndex(parent_container);
    }
    if(!hasValidParent || parent_container_index.isValid())
        attributes_model->addAttributeContainer(parent_container_index, std::move(attribute_container_copy));
}

AddAttributeContainerCommand::AddAttributeContainerCommand(const int &position, const QString name, const QModelIndex &node_index, const QModelIndex &container_index, AttributesModel *model):
    NodeChangedBaseCommand (node_index),
    position(position),
    name(name),
    attributes_model(model),
    parent_container_index(container_index),
    hasValidParent(container_index.isValid()),
    parent_container_name(attributes_model->getContainer(parent_container_index)->getFullName())
{
    setText("Added attribute container " + name + " to " + QString::fromStdString(NodesModel::getNodesModel(node_index)->getItem(node_index)->getName()));
}

void AddAttributeContainerCommand::redo()
{
    if(hasValidParent && !parent_container_index.isValid())
    {
        const MemoryNode* node = attributes_model->getParentMemoryNode();
        AttributeContainer* parent_container = node->getAttributeContainer()->getAttributeContainer(parent_container_name);
        parent_container_index = attributes_model->getIndex(parent_container);
    }

    if(!hasValidParent || parent_container_index.isValid())
    {
        if(attribute_container_copy)
            attributes_model->addAttributeContainer(position, parent_container_index, std::move(attribute_container_copy));
        else
            attributes_model->addAttributeContainer(position, parent_container_index, name);
    }
}

void AddAttributeContainerCommand::undo()
{
    if(!hasValidParent || parent_container_index.isValid())
        attribute_container_copy = attributes_model->removeAttributeContainer(parent_container_index, name);
}

AddChildrenCommand::AddChildrenCommand(const std::vector<std::string> &types, const QModelIndex &nodes_model_parent):
    NodeChangedBaseCommand (nodes_model_parent),
    types(types), model(NodesModel::getNodesModel(nodes_model_parent)), parent_index(nodes_model_parent),
    parent_node(model->getItem(parent_index))
{
    QString parent_name;
    for(auto& str: model->getItem(parent_index)->nodeLocation())
    {
        parent_name += QString::fromStdString(str) + "_";
    }
    setText("Added " + QString::fromStdString(join(types, "/")) + " to " + parent_name);
}

void AddChildrenCommand::redo()
{
    if(!parent_index.isValid())
    {
        parent_index = model->getIndex(parent_node).siblingAtColumn(0);
    }
    if(parent_index.isValid())
    {
        if(node_copy)
        {
            model->insertChild(0, std::move(node_copy), parent_index);
        }
        else
        {
            QModelIndex tmp_parent_index = parent_index;
            for(const auto& type: types)
            {
                MemoryNode* parent_node = model->getItem(tmp_parent_index);
                if(parent_node->hasChild(type) && parent_node->getValidator()->getChild(type)->isSpecialSequence())
                {
                    tmp_parent_index = model->getIndex(parent_node->getChild(type)).siblingAtColumn(0);
                }
                else
                {
                    const int pos = 0; // insert at beginning
                    model->addChild(pos, QString::fromStdString(type), tmp_parent_index);
                    const QModelIndex& child_index = model->index(pos, 0, tmp_parent_index);
                    tmp_parent_index = child_index;
                }
            }
        }
    }
}

void AddChildrenCommand::undo()
{
    if(!parent_index.isValid())
    {
        parent_index = model->getIndex(parent_node).siblingAtColumn(0);
    }
    if(parent_index.isValid())
    {
        node_copy = model->removeChild(0, parent_index);
    }
}

DummyCommand::DummyCommand(const QModelIndex &index, const QString &text):
    NodeChangedBaseCommand (index)
{
    setText(text);
}
